package com.pai4j.pgc.service.community;
import com.pai4j.common.bean.PAIPageResponseBeanUtil;
import com.pai4j.common.enums.CommonStatusEnum;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.common.util.ListUtil;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.domain.community.vo.request.CCOpenSourceSaveVO;
import com.pai4j.domain.community.vo.response.cc.CCOpenSourceDetailVO;
import com.pai4j.domain.community.vo.response.cc.CTCPathBaseVO;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.pgc.dao.community.ICCOpenSourceDAO;
import com.pai4j.pgc.entity.community.CCOpenSourceEntity;
import com.pai4j.remote.user.UserServiceClient;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 *
 * @author: CYM-pai
 * @date: 2025/10/27 19:47
 **/
@Service
public class CCOpenSourceService {


    @Autowired
    private ICCOpenSourceDAO openSourceDAO;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private CCPathService pathService;
    @Autowired
    private UserServiceClient userServiceClient;

    /**
     * 提交开源项目资源
     * @param openSourceSaveVO
     * @param userId
     * @return
     */
    public Long submit(CCOpenSourceSaveVO openSourceSaveVO, String userId) {
        Long ccId = openSourceSaveVO.getCcId();
        List<CCOpenSourceEntity> list =  openSourceDAO.findAllByCcIdAndUrl(ccId, openSourceSaveVO.getUrl());
        Assert.isTrue(CollectionUtils.isEmpty(list), "项目已存在！");
        CCOpenSourceEntity openSourceEntity = this.covertEntity(openSourceSaveVO, userId);
        openSourceEntity = openSourceDAO.save(openSourceEntity);
        return openSourceEntity.getId();
    }

    public void setRecommend(Long id, Boolean recommendStatus, String userId) {
        CCOpenSourceEntity openSourceEntity = openSourceDAO.findById(id).orElse(null);
        Assert.isTrue(openSourceEntity != null, "项目不存在！");
        openSourceEntity.setRecommend(recommendStatus);
        openSourceEntity.setUpdateBy(userId);
        openSourceEntity.setUpdateDate(null);
        if (ObjectUtils.equals(recommendStatus, true)) {
            openSourceEntity.setRecommendDate(new Date());
        }
        openSourceEntity = openSourceDAO.save(openSourceEntity);

        /**
         * 刷新开源项目缓存
         */
        this.refreshOpenSourceDetailCache(openSourceEntity);
    }

    public void review(Long id, String status, String userId) {
        CCOpenSourceEntity openSourceEntity = openSourceDAO.findById(id).orElse(null);
        Assert.isTrue(openSourceEntity != null, "项目不存在！");
        openSourceEntity.setStatus(status);
        openSourceEntity.setUpdateBy(userId);
        openSourceEntity.setUpdateDate(null);
        openSourceEntity = openSourceDAO.save(openSourceEntity);
        /**
         * 刷新开源项目缓存
         */
        this.refreshOpenSourceDetailCache(openSourceEntity);

        this.refreshAllNewOpenSourceCache(id);
    }

    /**
     * 社区最新开源项目列表查询
     *
     * @param ccId
     * @param pageNo
     * @param pageSize
     * @return
     */
    public PAIPageResponseBeanUtil<List<CCOpenSourceDetailVO>> pageList(Long ccId, int pageNo, int pageSize) {
        String key = CTCRedisKeyEnum.CTC_OPEN_SOURCE_NEW_ZSET_CACHE.getKey(ccId.toString());
        if (!redisUtil.exists(key)) {
            this.refreshAllNewOpenSourceCache(ccId);
        }
        Long total = redisUtil.zsize(key);
        if (total == 0) {
            return PAIPageResponseBeanUtil.success(pageNo, pageSize, total, Collections.emptyList());
        }
        Set<String> osIdList = redisUtil.zreverseRange(key, (pageNo - 1) * pageSize, pageNo * pageSize);
        List<CCOpenSourceDetailVO> list = batchGetOpenSourceListFromCache(osIdList.stream().map(Long::valueOf).collect(Collectors.toList()));
        return PAIPageResponseBeanUtil.success(pageNo, pageSize, total, list);
    }

    /**
     * 刷新社区全部开源项目列表
     * @param ccId
     */
    public void refreshAllNewOpenSourceCache(Long ccId) {
        List<CCOpenSourceEntity> openSourceEntities =
                openSourceDAO.findAllByCcIdAndStatusOrderByIdAsc(ccId, CommonStatusEnum.PUBLISHED.getStatus());
        Set<ZSetOperations.TypedTuple<String>> tuples = openSourceEntities.stream().map(os -> {
            String value = String.valueOf(os.getId());
            Double score = Double.valueOf(os.getId());
            return new DefaultTypedTuple<>(value, score);
        }).collect(Collectors.toSet());
        String key = CTCRedisKeyEnum.CTC_OPEN_SOURCE_NEW_ZSET_CACHE.getKey(ccId.toString());
        redisUtil.zadd(key, tuples, CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getExpireTime());
    }

    public List<CCOpenSourceDetailVO> recommendList(Long pathId, int size) {
        List<CCOpenSourceEntity> list = openSourceDAO.findLimitRecommendList(pathId, size);
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        return list.stream().map(os -> {
            CCOpenSourceDetailVO openSourceDetail = new CCOpenSourceDetailVO();
            BeanUtils.copyProperties(os, openSourceDetail);
            openSourceDetail.setLanguages(ListUtil.string2List(os.getLanguage()));
            if (ObjectUtils.equals(os.getRecommend(), true) && os.getRecommendDate() != null) {
                openSourceDetail.setRecommendTime(os.getRecommendDate().getTime());
            }
            openSourceDetail.setCreateTime(os.getCreateDate().getTime());
            return openSourceDetail;
        }).collect(Collectors.toList());
    }

    public CCOpenSourceDetailVO refreshOpenSourceDetailCache(Long id) {
        CCOpenSourceEntity openSourceEntity = openSourceDAO.findById(id).orElse(null);
        return refreshOpenSourceDetailCache(openSourceEntity);
    }
    public CCOpenSourceDetailVO refreshOpenSourceDetailCache(CCOpenSourceEntity openSourceEntity) {
        if (openSourceEntity == null) {
            return null;
        }
        String key = CTCRedisKeyEnum.CTC_OPEN_SOURCE_DETAIL_CACHE.getKey(openSourceEntity.getId().toString());
        CCOpenSourceDetailVO openSourceDetail = new CCOpenSourceDetailVO();
        BeanUtils.copyProperties(openSourceEntity, openSourceDetail);
        openSourceDetail.setLanguages(ListUtil.string2List(openSourceEntity.getLanguage()));
        redisUtil.set(key, JsonUtil.toJsonString(openSourceDetail), CTCRedisKeyEnum.CTC_OPEN_SOURCE_DETAIL_CACHE.getExpireTime());
        return openSourceDetail;
    }

    public List<CCOpenSourceDetailVO> batchGetOpenSourceListFromCache(List<Long> osIdList) {
        List<String> idList = osIdList.stream().map(id ->
                CTCRedisKeyEnum.CTC_OPEN_SOURCE_DETAIL_CACHE.getKey(id.toString())).collect(Collectors.toList());
        List<String> caches = redisUtil.mget(idList);
        List<CCOpenSourceDetailVO> list = new ArrayList<>();
        for (int i = 0; i < osIdList.size(); i++) {
            Long id = osIdList.get(i);
            String cache = caches.get(i);
            CCOpenSourceDetailVO openSourceDetailVO;
            if (StringUtils.isNotBlank(cache)) {
                openSourceDetailVO = JsonUtil.fromJson(cache, CCOpenSourceDetailVO.class);
            } else {
                openSourceDetailVO = refreshOpenSourceDetailCache(id);
            }
            list.add(openSourceDetailVO);
        }
        return list;
    }

    public PAIPageResponseBeanUtil<List<CCOpenSourceDetailVO>> adminPageList(Long ccId, String title, List<String> statusList,
                                                             Boolean recommend, int pageNo, int pageSize) {
        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Order.desc("id")));
        Specification<CCOpenSourceEntity> specification = buildOpenSourceQueryCondition(ccId, title, statusList, recommend);
        Page<CCOpenSourceEntity> openSourceEntityPage = openSourceDAO.findAll(specification, pageable);
        if (CollectionUtils.isEmpty(openSourceEntityPage.getContent())) {
            return PAIPageResponseBeanUtil.success(pageNo, pageSize, openSourceEntityPage.getTotalElements(), Collections.emptyList());
        }
        List<CCOpenSourceEntity> openSourceEntities = openSourceEntityPage.getContent();
        Set<Long> ccIdList = openSourceEntities.stream().map(CCOpenSourceEntity::getCcId).collect(Collectors.toSet());
        List<String> userIdList = openSourceEntities.stream().map(CCOpenSourceEntity::getCreateBy).collect(Collectors.toList());
        Map<Long, CTCPathBaseVO> pathMap = pathService.batchGetPathBaseFromCache(new ArrayList<>(ccIdList));
        Map<String, UserBaseResponseInfoVO> authorMap = userServiceClient.batchGetUserInfoFromCache(userIdList);
        List<CCOpenSourceDetailVO> list = openSourceEntities.stream().map(os -> {
            CCOpenSourceDetailVO openSourceDetail = new CCOpenSourceDetailVO();
            BeanUtils.copyProperties(os, openSourceDetail);
            if (ObjectUtils.equals(os.getRecommend(), true)) {
                openSourceDetail.setRecommendTime(os.getRecommendDate().getTime());
            }
            if (StringUtils.isNotBlank(os.getLanguage())) {
                openSourceDetail.setLanguages(ListUtil.string2List(os.getLanguage()));
            }
            openSourceDetail.setCcName(pathMap.get(os.getCcId()).getName());
            openSourceDetail.setAuthor(authorMap.get(os.getCreateBy()));
            return openSourceDetail;
        }).collect(Collectors.toList());
        return PAIPageResponseBeanUtil.success(pageNo, pageSize, openSourceEntityPage.getTotalElements(),list);
    }

    private Specification<CCOpenSourceEntity> buildOpenSourceQueryCondition(Long ccId, String title,
                                                                            List<String> statusList,
                                                                            Boolean recommend) {
        Specification<CCOpenSourceEntity> specification = new Specification<CCOpenSourceEntity>() {
            @Override
            public Predicate toPredicate(Root<CCOpenSourceEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (ccId != null) {
                    predicates.add(criteriaBuilder.equal(root.get("ccId").as(Long.class), ccId));
                }
                if (recommend != null) {
                    predicates.add(criteriaBuilder.equal(root.get("recommend").as(Boolean.class), recommend));
                }
                if (StringUtils.isNotBlank(title)) {
                    predicates.add(
                            criteriaBuilder.or(
                                    criteriaBuilder.like(root.get("title").as(String.class), "%" + title + "%")));
                }
                if (CollectionUtils.isNotEmpty(statusList)) {
                    CriteriaBuilder.In<String> in = criteriaBuilder.in(root.get("status"));
                    for(String status : statusList) {
                        in.value(status);
                    }
                    predicates.add(in);
                }
                Predicate[] pre = new Predicate[predicates.size()];
                criteriaQuery.where(predicates.toArray(pre));
                return criteriaQuery.getRestriction();
            }
        };
        return specification;
    }

    private CCOpenSourceEntity covertEntity(CCOpenSourceSaveVO openSourceSaveVO, String userId) {
        CCOpenSourceEntity openSourceEntity = new CCOpenSourceEntity();
        BeanUtils.copyProperties(openSourceSaveVO, openSourceEntity);
        openSourceEntity.setCreateBy(userId);
        openSourceEntity.setCreateDate(new Date());
        openSourceEntity.setRecommend(false);
        openSourceEntity.setStatus(CommonStatusEnum.NEW.getStatus());
        return openSourceEntity;
    }
}
